package org.jallinone.commons.server;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Properties;

import javax.sql.DataSource;

import org.openswing.swing.message.receive.java.Response;

import co.edu.udistrital.erpgrid.interceptor.InterceptorBeanFactory;
import co.edu.udistrital.erpgrid.jaio.commons.JaioObjectVO;
import co.edu.udistrital.erpgrid.jaio.commons.SimpleDataSource;

/**
 * <p>
 * Title: JAllInOne ERP/CRM application
 * </p>
 * <p>
 * Description: Beans factory based on simple beans instantiation.
 * </p>
 * <p>
 * Copyright: Copyright (C) 2006 Mauro Carniel
 * </p>
 * 
 * <p>
 * This file is part of JAllInOne ERP/CRM application. This application is free
 * software; you can redistribute it and/or modify it under the terms of the
 * (LGPL) Lesser General Public License as published by the Free Software
 * Foundation;
 * 
 * GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999
 * 
 * This application is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * The author may be contacted at: maurocarniel@tin.it
 * </p>
 * 
 * @author Mauro Carniel
 * @version 1.0
 */
public class SimpleBeansFactory implements BeansFactory {

	/**
	 * Init this factory.
	 */
	public void initBeansFactory(Properties p) throws Throwable {
	}

	/**
	 * bandera para verificar que no se este ejecutando desde un nodo
	 */
	private transient boolean ejecutor = false;

	/**
	 * Create a bean of the specified class.
	 * 
	 * @param clazz
	 *            bean's class type
	 * @return an instance of the bean
	 * @throws Throwable
	 *             in case of errors
	 */
	public Object getBean(Class clazz) throws Throwable {
		return getBean(clazz, new SimpleDataSource());
	}
	
	/**
	 * Obtiene el constructor solicitado a la fabrica
	 * @param clazz
	 * @param ejecutor
	 * @return
	 * @throws Throwable
	 */
	public Object getBean(Class clazz, boolean ejecutor) throws Throwable {
		this.ejecutor = ejecutor; 
		return getBean(clazz, new SimpleDataSource());
	}

	/**
	 * Generador de beans de la factoria y donde se intercepta para realizar las peticiones y enviarlas a través de los nodos
	 * @param clazz
	 * @param ds
	 * @return
	 * @throws Throwable
	 */
	private Object getBean(Class clazz, final SimpleDataSource ds)
			throws Throwable {
		if (!clazz.isInterface())
			throw new Exception(clazz.getName() + " is not an interface");
		final Class clase = clazz;
		String beanName = clazz.getName() + "Bean";
		Class beanClass = Class.forName(beanName);
		final Object bean = beanClass.newInstance();

		InvocationHandler handler = new InvocationHandler() {

			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				try {
					Object retValue;
					if (ejecutor) {
						retValue = method.invoke(bean, args);
					} else {
						JaioObjectVO objectVO = new JaioObjectVO();
						objectVO.setArgs(args);
						objectVO.setBean(bean);
						objectVO.setClazz(clase);
						objectVO.setMethod(method);
						retValue = new InterceptorBeanFactory().invoke(objectVO, ds);
					}
					if (!ejecutor && !method.getName().equals("setDataSource")
							&& !method.getName().equals("setConn")
							&& !method.getName().equals("getConn")) {
						
						if (ds != null && ds.getCurrentConn() != null) {
							ds.getCurrentConn().commit();
							ds.releaseConnection(ds.getCurrentConn());
						}
					}					
					
					
					return retValue;
				} catch (Throwable e) {
					if (e instanceof InvocationTargetException)
						e = ((InvocationTargetException) e)
								.getTargetException();
					// e.printStackTrace();

					// in case of error the connection must be rolled
					// back...
					if (ds != null && ds.getCurrentConn() != null) {
						ds.getCurrentConn().rollback();
						ds.releaseConnection(ds.getCurrentConn());

					}
					// throw e;
					Response res = null;
					if (Response.class.isAssignableFrom(method.getReturnType())) {
						res = (Response) method.getReturnType().newInstance();
						method.getReturnType()
								.getMethod("setErrorMessage",
										new Class[] { String.class })
								.invoke(res, new Object[] { e.getMessage() });
						method.getReturnType()
								.getMethod("setError",
										new Class[] { Boolean.TYPE })
								.invoke(res, new Object[] { Boolean.TRUE });
						return res;
					} else
						throw e;
				}
			}

		};
		Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(),
				new Class[] { clazz }, handler);
		fillInBean(bean, beanClass, ds);

		return proxy;
	}

	private void fillInBean(Object bean, Class beanClass, SimpleDataSource ds)
			throws Throwable {
		// set datasource property (if exists)
		try {
			beanClass.getMethod("setDataSource",
					new Class[] { DataSource.class }).invoke(bean,
					new Object[] { ds });
		} catch (Exception e) {
		}

		// analyze other properties...
		Method[] setters = beanClass.getMethods();
		Method m = null;
		for (int i = 0; i < setters.length; i++) {
			m = setters[i];
			if (m.getName().startsWith("set")
					&& m.getParameterTypes().length == 1
					&& m.getParameterTypes()[0].getName().endsWith("Bean")) {
				// found an injected bean...
				if (beanClass.getName().endsWith("Bean")) {
					Object injectedBean = m.getParameterTypes()[0]
							.newInstance();
					fillInBean(injectedBean, m.getParameterTypes()[0], ds);
					m.invoke(bean, new Object[] { injectedBean });
				}
			}
		}
	}

	/**
	 * Destroy this factory.
	 */
	public void destroyBeansFactory() throws Throwable {

	}

}
